/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/

#include "MainApplication.hpp"
#include "chunkManager.hpp"

#include <OgreOverlayManager.h>
#include "textRenderer.hpp"

namespace minecraft {

    MainApplication::MainApplication(void) {
        //just so we can be certain that an object exists
        sBlockManager::Instance();
        sChunkManager::Instance();
        lastPosition = NULL;
        renderer = NULL;
    }

    MainApplication::~MainApplication(void) {
        sChunkManager::Destroy();
    }

    void MainApplication::createScene(void) {
        //skydome
        mSceneMgr->setSkyDome(true, "minecraft/SkyDome", 2, 8, 100); //skype dome
        mSceneMgr->setFog(Ogre::FOG_LINEAR, Ogre::ColourValue(0.5, 0.5, 0.5), 0.05, 0.0, 200); //grey fog
        mCamera->setFarClipDistance(P_CHUNKSIZE * 16); //view as much as 8 chunks
        mCamera->setNearClipDistance(0.01);

        //get cursor image => material
        Ogre::MaterialPtr cMaterial = Ogre::MaterialManager::getSingleton().create("cursor", "minecraft");
        cMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("cursor.png");
        cMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
        cMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
        cMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
        cMaterial->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);

        //overlay container for the cursor
        Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();
        Ogre::OverlayContainer* cursor = static_cast<Ogre::OverlayContainer*> (overlayManager.createOverlayElement("Panel", "cursor"));
        cursor->setMetricsMode(Ogre::GMM_PIXELS);
        cursor->setHorizontalAlignment(Ogre::GHA_CENTER);
        cursor->setVerticalAlignment(Ogre::GVA_CENTER);
        cursor->setDimensions(9, 9);
        cursor->setMaterialName("cursor");
        
        //create an overlay, and add the cursor
        Ogre::Overlay* overlay = overlayManager.create("main_overlay");       
        overlay->add2D(cursor);
        overlay->show();
        
        //construct textrenderer once
        new TextRenderer();
        TextRenderer::getSingleton().addTextBox("txtExit", "", 10, 10, 100, 20, Ogre::ColourValue::Red, "32");

        Ogre::Light* directionalLight = mSceneMgr->createLight("directionalLight");
        directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
        directionalLight->setDiffuseColour(Ogre::ColourValue(.25, .25, 0));
        directionalLight->setSpecularColour(Ogre::ColourValue(.25, .25, 0));
        directionalLight->setDirection(Ogre::Vector3(0, -1, 1));

		new boost::thread(boost::bind(&MainApplication::getWorker, this));
		this->started = false;
		this->rounds = 0;

        mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
        Ogre::Light* l = mSceneMgr->createLight("MainLight");
        l->setPosition(0, 0, 0);
    }
	
	void MainApplication::getAdd(int x, int y, int z) {
		coordinate c;
		c.x = x;
		c.y = y;
		c.z = z;
		
		this->getList_lock.lock();
			this->getList.insert(c);
		this->getList_lock.unlock();
	}
	
	void MainApplication::getWorker() {
		//take one from the list and get the chunks for it.
		int x,y,z = 0;
		
		while(!this->mShutDown) {
			bool doGet = false;
			boost::thread::yield();
			
			this->getList_lock.lock();

				boost::unordered_set<coordinate>::const_iterator mGet = getList.begin();
				//TODO: Max Unsigend Int Value here
				unsigned int mDiff = 99999;
			
				boost::unordered_set<coordinate>::const_iterator toGet = getList.begin();
				
				if (toGet != getList.end()) {
					for (toGet = getList.begin();toGet != getList.end();toGet++) {
						doGet = true;
					
						x = toGet->x;
						y = toGet->y;
						
						int dx = x - this->currentPosX;
						int dy = y - this->currentPosY;
						
						unsigned int diff = dx * dx + dy * dy;
						
						if (diff >= mDiff) {
							continue;
						} else {
							mDiff = diff;
							mGet = toGet;
						}
					}
					
					x = mGet->x;
					y = mGet->y;
					z = mGet->z;
					
					std::cout << "Loading" << x << ":" << y << ":" << z << "\n";
					
					mGet = getList.erase(mGet);
				} else {
					 boost::thread::yield();
				}
			this->getList_lock.unlock();
			
			boost::thread::yield();
			
			if (doGet) {
			
				//std::cout << "Get: " << x << ":" << y << ":" << z << "\n";
				
				sChunkManager::Instance()->getChunk(x+1, y, z);
				boost::thread::yield();
				sChunkManager::Instance()->getChunk(x-1, y, z);
				boost::thread::yield();
			
				sChunkManager::Instance()->getChunk(x, y+1, z);
				boost::thread::yield();
				sChunkManager::Instance()->getChunk(x, y-1, z);
				boost::thread::yield();
			
				sChunkManager::Instance()->getChunk(x, y, z+1);
				boost::thread::yield();
				sChunkManager::Instance()->getChunk(x, y, z-1);
				boost::thread::yield();
			
				sChunkManager::Instance()->getChunk(x, y, z);
				boost::thread::yield();
			
				coordinate c;
				c.x = x;
				c.y = y;
				c.z = z;
			
				this->renderList_lock.lock();
					this->renderList.insert(c);
				this->renderList_lock.unlock();			
			}
		}
	}
	
	void MainApplication::doDraw(int x, int y, int z) {
		this->getAdd(x, y, z);
	}
	
	void MainApplication::doRendering()
	{
		unsigned long long t1;
		unsigned long long t2;
		
		if (this->renderer != NULL && this->renderer->done) {
			//std::cout << "Rendering finished\n";
		
			this->drawList.insert(this->renderer->rChunk);
			delete this->renderer;
			
			this->renderer = NULL;
		}
	
		if (this->renderer == NULL) {
			int rx, ry, rz = 0;
			bool doRender = false;
			
			renderList_lock.lock();
				boost::unordered_set<coordinate>::const_iterator toRender = renderList.begin();
				boost::unordered_set<coordinate>::const_iterator mRender = renderList.begin();
				
				unsigned int mDiff = 99999;
				
				int dx, dy;
				unsigned int diff;
				
				if (toRender != renderList.end()) {
					doRender = true;
					for(toRender = renderList.begin();toRender != renderList.end(); toRender++) {
						rx = toRender->x;
						ry = toRender->y;
						
						dx = rx - this->currentPosX;
						dy = ry - this->currentPosY;
						
						diff = dx * dx + dy * dy;
						
						if (diff < mDiff) {
							mDiff = diff;
							mRender = toRender;
						}
					}
					
					rx = mRender->x;
					ry = mRender->y;
					rz = mRender->z;
					
					renderList.erase(mRender);
				}
			renderList_lock.unlock();
			
			if (doRender) {
				t1 = rdtsc();
			
				this->renderer = new RenderHelper(rx, ry, rz);
				Call(this->renderer);
				
				t2 = rdtsc();
				//std::cout << "Rendering Part1:" << (t2-t1) / 1000 << "\n";
			}
		} else {
			t1 = rdtsc();
				Call(this->renderer);
			t2 = rdtsc();
			//std::cout << "Rendering Part2:" << (t2-t1) / 1000 << "\n";
		}
		
		return;
	}
	
    bool MainApplication::frameRenderingQueued(const Ogre::FrameEvent& evt) {
        bool val = BaseApplication::frameRenderingQueued(evt);
        if (!val)
            return false;

		this->rounds++;
			
        Ogre::Vector3 pos = mCamera->getPosition();
        int px = pos.x / P_CHUNKSIZE;
        int py = pos.y / P_CHUNKSIZE;
        int pz = pos.z / P_CHUNKSIZE;
	
	//check if c_x == 0 but x < 0 && x > -64
        if (pos.x < 0 && px == 0) px = -1;
        if (pos.y < 0 && py == 0) py = -1;
        if (pos.z < 0 && pz == 0) pz = -1;
	
		unsigned long long t1;
		unsigned long long t2;
	
		bool drawn = false;
	
		boost::unordered_set<Ogre::ManualObject*>::const_iterator toDraw = drawList.begin();
		if (toDraw != drawList.end()) {
			drawn = true;
			
			std::vector<std::string> parts = split((*toDraw)->getName().substr(6), '.', false);
			for(int i = 0; i < 3; ++i)
				std::replace(parts[i].begin(), parts[i].end(), 'F', '-');
			
			int x = boost::lexical_cast<int>(parts[0]);
			int y = boost::lexical_cast<int>(parts[1]);
			int z = boost::lexical_cast<int>(parts[2]);
			
			Ogre::SceneNode* activeNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(x*P_CHUNKSIZE, y*P_CHUNKSIZE, z*P_CHUNKSIZE));
			activeNode->attachObject((*toDraw));

			//put stuff in lists
			activeChunks.push_back((*toDraw));
			activeNodes.push_back(activeNode);
			
			drawList.erase(toDraw);
		}
		
		if (!drawn) {
			Sequencing(this->doRendering());
		}
	
		Ogre::Vector3 *playerPos = new Ogre::Vector3(px,py,pz);
	
        if (activeNodes.empty() && !this->started) {	  
            lastPosition = new Ogre::Vector3(px, py, pz);
			
			for(int x = px-3; x <= px+3; ++x){
			  for(int y = py-3; y <= py+3; ++y){
				for(int z = pz-3; z <= pz+3; ++z){
					this->started = true;
					this->doDraw(x, y, z);
				}
			  }
			}
        }
        
        if (px != lastPosition->x || py != lastPosition->y || pz != lastPosition->z) {
			this->currentPosX = px;
			this->currentPosY = py;
			this->currentPosZ = pz;
			std::list<Ogre::SceneNode*>::iterator it;
			for (it=activeNodes.begin(); it!=activeNodes.end(); ++it){
				Ogre::MovableObject *chunkMesh = (*it)->getAttachedObject(0);
				std::vector<std::string> parts = split(chunkMesh->getName().substr(6), '.', false);
				for(int i = 0; i < 3; ++i)
					std::replace(parts[i].begin(), parts[i].end(), 'F', '-');
					
				Ogre::Vector3 *chunkPos = new Ogre::Vector3(boost::lexical_cast<int>(parts[0]),boost::lexical_cast<int>(parts[1]),boost::lexical_cast<int>(parts[2]));
				if(chunkPos->squaredDistance(*playerPos) > 64){
					std::cout << "deleting chunk_mesh" << chunkMesh->getName() << " at " << *chunkPos << " distance "<< chunkPos->squaredDistance(*playerPos) <<"\n";
					(*it)->detachObject(chunkMesh->getName());
					delete chunkMesh;
					mSceneMgr->getRootSceneNode()->removeAndDestroyChild((*it)->getName());
					it = activeNodes.erase(it);
				}

				delete chunkPos;
		  }
		  
		  Ogre::Vector3 *dp = new Ogre::Vector3(px - lastPosition->x, py - lastPosition->y, pz - lastPosition->z);
		  
		  std::cout << "delta position: " << *dp << "\n";
		  if(dp->x != 0){
			int x = px + dp->x * 2;
			for(int y = py-3; y <= py+3; ++y){
				for(int z = pz-3; z <= pz+3; ++z){
					this->doDraw(x,y,z);
				}
			}
		  }
		  
			if(dp->y != 0){
				for(int x = px-3; x <= px+3; ++x){
					int y = py + dp->y * 2;
					for(int z = pz-3; z <= pz+3; ++z){
						this->doDraw(x,y,z);
					}
				}
			}

			if(dp->z != 0){
				for(int x = px-3; x <= px+3; ++x){
					for(int y = py-3; y <= py+3; ++y){
						int z = pz + dp->z * 2;
							this->doDraw(x,y,z);
					}
				}
			}
			mSceneMgr->getRootSceneNode()->_update(true,false);
			mSceneMgr->getRootSceneNode()->_updateBounds();
			/*activeNode->detachObject(activeChunk);
			delete activeChunk;
			mSceneMgr->getRootSceneNode()->removeAndDestroyChild(activeNode->getName());

			sChunkManager::Instance()->preloadAdd(px, py, pz);
			sChunkManager::Instance()->preloadJoin();
			activeChunk = sChunkManager::Instance()->renderChunk(px, py, pz);
			activeNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(px*P_CHUNKSIZE, py*P_CHUNKSIZE, pz * P_CHUNKSIZE));
			activeNode->attachObject(activeChunk);*/
			delete dp;
			lastPosition->x = px;
			lastPosition->y = py;
			lastPosition->z = pz;
        }
	
		delete playerPos;
	
        return val;
    }
    
    std::vector<std::string> MainApplication::split(std::string string, const char delimiter, bool keepempty) {
        std::stringstream s(string);
        std::string t;
        std::vector<std::string> r;
        while (std::getline(s, t, delimiter)) {
            if (keepempty || t.size() != 0) {
                r.push_back(t);
            }
        }
     
        return r;
    }
    
}

using namespace minecraft;

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else

    int main(int argc, char *argv[])
#endif
    {
        // Create application object
        MainApplication app;

        try {
            app.go();
        } catch (Ogre::Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
            MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
            std::cerr << "An exception has occured: " <<
                e.getFullDescription().c_str() << std::endl;
#endif
        }

        return 0;
    }

#ifdef __cplusplus
}
#endif
